package br.com.ps.bombertournament.game;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.badlogic.gdx.Gdx;

import flexjson.JSONDeserializer;

/**
 * Responsible to help a easy way to load and interpreted a configurations file
 * 
 * @author Rennan Stefan Boni - rennanboni@gmail.com
 */
public class WrapperMap {
	
	private static final String PARENT_PROPERTY = "parent";
	private WrapperMap parent;
	private Map<String, Object> map;
	
	/**
	 * A constructor with map of configurations properties and the parent
	 * 
	 * @param map Configuration properties loaded
	 * @param parent Parent of the current node
	 */
	public WrapperMap(Map<String, Object> map, WrapperMap parent) {
		this.map = map;
		this.parent = parent;
	}

	/**
	 * Constructor with configurations properties 
	 * 
	 * @param map Configuration properties
	 */
	public WrapperMap(Map<String, Object> map) {
		this.map = map;
		
		if (containsKey(PARENT_PROPERTY)) {
			String input = Gdx.files.internal(getString(PARENT_PROPERTY)).readString();
			this.parent = new WrapperMap(new JSONDeserializer<Map<String, Object>>().deserialize(input));
		}
	}
	
	/**
	 * Return a boolean property
	 * 
	 * @param key Property name
	 * @return Return a boolean property, can return null
	 */
	public Boolean getBoolean(String key) {
		Boolean result = null;
		
		Object value = getObject(key);
		if (value != null) {
			if (value instanceof Boolean) {
				result = (Boolean) value;
			} else {
				result = Boolean.parseBoolean(value.toString());
			}
		}
		
		return result;
	}
	
	/**
	 * Return a boolean property
	 * 
	 * @param key Property name
	 * @param defaultValue Default value
	 * @return Return a boolean property, if the property does not exist will be returned the default value
	 */
	public Boolean getBoolean(String key, boolean defaultValue) {
		return key != null && containsKey(key) ? getBoolean(key) : defaultValue;
	}
	
	/**
	 * Return a boolean property
	 * 
	 * @param key Property name
	 * @param defaultValue Default value
	 * @return Return a boolean property, can return null
	 */
	public Double getDouble(String key) {
		Double result = null;
		
		Object value = getObject(key);
		if (value != null) {
			if (isMathExpression(value)) {
				Double resultExpression = calculateMathExpression(value);
				if (resultExpression != null) {
					result = resultExpression;
				}
			} else if (value instanceof Double) {
				result = (Double) value;
			} else {
				result = Double.parseDouble(value.toString());
			}
		}
		
		return result;
	}
	
	/**
	 * Return a float property
	 * 
	 * @param key Property name
	 * @return Return a float property, can return null
	 */
	public Float getFloat(String key) {
		Float result = null;
		
		Object value = getObject(key);
		if (value != null) {
			if (isMathExpression(value)) {
				Double resultExpression = calculateMathExpression(value);
				if (resultExpression != null) {
					result = resultExpression.floatValue();
				}
			} else if (value instanceof Float) {
				result = (Float) value;
			} else {
				result = Float.parseFloat(value.toString());
			}
		}
		
		return result;
	}
	
	/**
	 * Return a float property
	 * 
	 * @param key Property name
	 * @param defaultValue Default value
	 * @return Return a float property, if the property does not exist will be returned the default value
	 */
	public Float getFloat(String key, float defaultValue) {
		return key != null && containsKey(key) ? getFloat(key) : defaultValue;
	}
	
	/**
	 * Return a String property
	 * 
	 * @param key Property name
	 * @return Return a String property, can return null
	 */
	public String getString(String key) {
		String result = null;
		
		if (containsKey(key)) {
			Object value = getObject(key);
		
			if (value != null) {
				if (value instanceof String) {
					result = (String) value;
				} else {
					result = value.toString();
				}
			}
		}
		
		return result;
	}
	
	/**
	 * Return a String property
	 * 
	 * @param key Property name
	 * @return Return a String property, can return null
	 */
	public String getString(String key, String defaultValue) {
		return key != null && containsKey(key) ? getString(key) : defaultValue;
	}
	
	/**
	 * Return a Integer property
	 * 
	 * @param key Property name
	 * @return Return a Integer property, can return null
	 */
	public Integer getInteger(String key) {
		Integer result = null;
		
		Object value = getObject(key);
		if (value != null) {
			if (isMathExpression(value)) {
				Double resultExpression = calculateMathExpression(value);
				if (resultExpression != null) {
					result = resultExpression.intValue();
				}
			} else if (value instanceof Integer) {
				result = (Integer) value;
			} else {
				result = Integer.parseInt(value.toString());
			}
		}
			
		return result;
	}
	
	/**
	 * Return a Integer property
	 * 
	 * @param key Property name
	 * @param defaultValue Default value
	 * @return Return a Integer property, if the property does not exist will be returned the default value
	 */
	public Integer getInteger(String key, Integer defaultValue) {
		return key != null && containsKey(key) ? getInteger(key) : defaultValue;
	}

	/**
	 * Calculate math expression
	 *  
	 * @param value Object that will be interpreted
	 * @return Return a double value, result of the calculation expression
	 */
	private Double calculateMathExpression(Object value) {
		String valueStr = value.toString();
		
		if (valueStr.charAt(0) == '=') {
			valueStr = valueStr.substring(1);
		}
		MathEvaluator mathEvaluator = new MathEvaluator(valueStr);
		
		List<String> parameters = getParameters(valueStr);
		if(parameters != null) {
			for (String parameter : parameters) {
				Double parameterValue = getDouble(parameter);
				
				mathEvaluator.addVariable(String.format("{%s}", parameter), parameterValue);
			}
		}
		
		return mathEvaluator.getValue();
	}
	
	/**
	 * Return {@link WrapperMap} property
	 *  
	 * @param key Property name
	 * @return Return a new {@link WrapperMap} with the configurations properties, can return null.
	 */
	public WrapperMap getMap(String key) {
		WrapperMap result = null;
		
		Object value = getObject(key);
		if (value != null) {
			result = new WrapperMap((Map<String, Object>) value, this);
		}
		return result;
	}
	
	/**
	 * Return a list of {@link WrapperMap}.
	 * 
	 * @param key Property name
	 * @return Return a list of {@link WrapperMap}, can return null.
	 */
	public List<WrapperMap> getList(String key) {
		List<WrapperMap> result = null;
		
		Object value = getObject(key);
		if (value != null) {
			result = new ArrayList<WrapperMap>();
			List<Map<String, Object>> values = (List<Map<String, Object>>) value;
			for (Map<String, Object> item : values) {
				result.add(new WrapperMap((Map<String, Object>) item, this));
			}
		}
		
		return result;
	}
	
	/**
	 * Indicate if exist the property name independent of value type.
	 * 
	 * @param key Property name 
	 * @return Return true if the property name exists, otherwise return false.
	 */
	public boolean containsKey(String key) {
		return getObject(key) != null;
	}
	
	/**
	 * Indicate if exist the property name independent of value type inside of map parameter
	 *  
	 * @param map The map parameter to be analyzed
	 * @param key Property name
	 * @return Return true if the property name exists, otherwise return false. 
	 */
	public boolean containsKey(Map<String, Object> map, String key) {
		return map.containsKey(key);
	}
	
	/**
	 * Indicate if the parameter value if a math expression
	 * 
	 * @param value Parameter value to be analyzed
	 * @return Return true if the parameter value if a math expression, otherwise return false
	 */
	private boolean isMathExpression(Object value) {
		return value != null ? value.toString().charAt(0) == '=' : false;
	}
	
	/**
	 * Analyzed the math expression and return all properties names founded
	 * 
	 * @param mathExpression Parameter value to be analyzed
	 * @return Return all properties names to be founded in the math expression
	 */
	public List<String> getParameters(String mathExpression) {
		List<String> result = new ArrayList<String>();
		
		if (mathExpression != null) {
			int startPosition = -1;
			while ((startPosition = mathExpression.indexOf('{', startPosition)) != -1) {
				int endPosition = mathExpression.indexOf('}', startPosition++);
				String parameter = mathExpression.substring(startPosition, endPosition);
				
				result.add(parameter);
				startPosition = endPosition;
			}
		}
		
		return result;
	}
	
	/**
	 * Return a Object property
	 * @param path Path of property name
	 * @return Return the object founded in the path parameter, can return null.
	 */
	public Object getObject(String path) {
		Object result = null;

		if (path != null) {
			String[] splitedKey = path.split("\\.");

			Map<String, Object> mapTemp = map;
			for (int i = 0; i < splitedKey.length; i++) {
				String key = splitedKey[i];
				
				if (containsKey(mapTemp, key)) {
					
					if (i < splitedKey.length - 1) {
						mapTemp = (Map<String, Object>) mapTemp.get(key);
					} else {
						result = mapTemp.get(key);
					}
				} else {
					break;
				}
			}
		}
		
		if (result == null && parent != null) {
			result = parent.getObject(path);
		}
		
		return result;
	}
	
	@Override
	public String toString() {
		return "WrapperMap [map=" + map + "]";
	}

}
